#include <QDebug>
#include <QCryptographicHash>
#include <QDateTime>
#include <QStringList>
#include <QUrl>
#include <QMap>
#include "oauth.h"

OAuth::OAuth(QObject *parent) :
    QObject(parent)
{
  _tokenSecret = "";
}

QString OAuth::hmacSha1(QByteArray key, QByteArray baseString)
{
    int blockSize = 64; // HMAC-SHA-1 block size, defined in SHA-1 standard
    if (key.length() > blockSize) { // if key is longer than block size (64), reduce key length with SHA-1 compression
        key = QCryptographicHash::hash(key, QCryptographicHash::Sha1);
    }

    QByteArray innerPadding(blockSize, char(0x36)); // initialize inner padding with char "6"
    QByteArray outerPadding(blockSize, char(0x5c)); // initialize outer padding with char "\"
    // ascii characters 0x36 ("6") and 0x5c ("\") are selected because they have large
    // Hamming distance (http://en.wikipedia.org/wiki/Hamming_distance)

    for (int i = 0; i < key.length(); i++) {
        innerPadding[i] = innerPadding[i] ^ key.at(i); // XOR operation between every byte in key and innerpadding, of key length
        outerPadding[i] = outerPadding[i] ^ key.at(i); // XOR operation between every byte in key and outerpadding, of key length
    }

    // result = hash ( outerPadding CONCAT hash ( innerPadding CONCAT baseString ) ).toBase64
    QByteArray total = outerPadding;
    QByteArray part = innerPadding;
    part.append(baseString);
    total.append(QCryptographicHash::hash(part, QCryptographicHash::Sha1));
    QByteArray hashed = QCryptographicHash::hash(total, QCryptographicHash::Sha1);
    return hashed.toBase64();
}

QString OAuth::signature(const QString &method, const QString &url, const QString &query, const QString &key, const QString &tokenKey)
{
  QString baseString = method;
  QString keyStr = key + "&";

  if (tokenKey.length() > 0) keyStr += tokenKey;

  baseString.append('&');
  baseString.append(QUrl::toPercentEncoding(url));
  baseString.append('&');
  baseString.append(QUrl::toPercentEncoding(query));

  QString sha1 = hmacSha1(keyStr.toUtf8(), baseString.toUtf8());

  return sha1;
}

QString OAuth::signature(const QString &method, const QString &url, QMap<QString, QString> params, const QString &key, const QString &tokenKey)
{
  QString query = "";
  QStringList list;

  QMap<QString, QString>::iterator i = params.begin();
  while (i != params.end()) {
    list.append(i.key() + "=" + i.value());
    i++;
  }

  qSort(list);

  for (int count = 0; count < list.count(); count++) {
    query = query + list[count] + "&";
  }
  query = query.left(query.length() - 1);

  return signature(method, url, query, key, tokenKey);
}

QMap<QString, QString> OAuth::generateParameters(const QString &nonce, const QString &timestamp)
{
  QMap<QString, QString> params;

  QString _nonce = nonce;
  QString _timestamp = timestamp;

  params["oauth_consumer_key"]     = _consumerKey;
  params["oauth_signature_method"] = "HMAC-SHA1";
  params["oauth_version"]          = "1.0";

  if (nonce.length() == 0) {
    _nonce = QString::number(qrand() % 100000);
    for (int count = 0; count < 6 - _nonce.length(); count++) {
      _nonce.insert(0, '0');
    }
  }
  params["oauth_nonce"]            = _nonce;

  if (timestamp.length() == 0)  _timestamp = QString::number(QDateTime::currentDateTime().toTime_t());
  params["oauth_timestamp"]        = _timestamp;

  return params;
}

QString OAuth::generateSignature(const QString &method, const QString &url, const QString &nonce, const QString &timestamp)
{
  QMap<QString, QString> params = generateParameters(nonce, timestamp);

  return signature(method, url, params, _consumerSecret);
}

QString OAuth::requestToken(const QString &method, const QString &url)
{
  QString sig = "";
  _tokenSecret = "";

  QMap<QString, QString> params = generateParameters();
  sig = signature(method, url, params, _consumerSecret);
  params["oauth_signature"] = sig;
  params["OAuth realm"] = "";

  QString _params = "";
  QMap<QString, QString>::iterator i = params.begin();
  while (i != params.end()) {
    _params = _params + i.key() + "=\"" + QUrl::toPercentEncoding(i.value()) + "\",";
    i++;
  }
  _params = _params.left(_params.length() - 1);

  QMap<QString, QString> headers;
  headers["Authorization"] = _params;

  _tokenKey = "";
  _tokenSecret = "";

  QNetworkReply *reply = ajax.post(url, headers, false);
  connect(reply, SIGNAL(finished()), this, SLOT(tokenFinished()));
  connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(tokenError(QNetworkReply::NetworkError)));

  return "";
}

void OAuth::tokenFinished()
{
   QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());

   QStringList list = QString::fromUtf8(reply->readAll()).split('&', QString::SkipEmptyParts);

   for (int count = 0; count < list.length(); count++) {
     QStringList value = list[count].split('=');
     if (value.length() != 2) continue;
     if (value[0] == "oauth_token") {
       _tokenKey = value[1];
     } else if (value[0] == "oauth_token_secret") {
       _tokenSecret = value[1];
     }
   }

   emit performAuth(_tokenKey);
}

void OAuth::tokenError(QNetworkReply::NetworkError error)
{
  Q_UNUSED(error);

//  QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());

}

void OAuth::accessFinished()
{
   QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
   QStringList list = QString::fromUtf8(reply->readAll()).split('&', QString::SkipEmptyParts);

   for (int count = 0; count < list.length(); count++) {
     QStringList value = list[count].split('=');
     if (value.length() != 2) continue;
     if (value[0] == "oauth_token") {
       _appKey = value[1];
     } else if (value[0] == "oauth_token_secret") {
       _appSecret = value[1];
     }
   }

   emit finishAccess();
}

void OAuth::accessError(QNetworkReply::NetworkError error)
{
  Q_UNUSED(error);

//  QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
//   qDebug() << reply->readAll();
}

QString OAuth::requestAccessToken(const QString &method, const QString &url)
{
  QString sig = "";
  QMap<QString, QString> params = generateParameters();
  params["oauth_token"] = _tokenKey;
  params["oauth_token_secret"] = _tokenSecret;
  params["oauth_verifier"] = _tokenVerifier;
  sig = signature(method, url, params, _consumerSecret, _tokenSecret);
  params["oauth_signature"] = sig;
  params["OAuth realm"] = "";

  QString _params = "";
  QMap<QString, QString>::iterator i = params.begin();
  while (i != params.end()) {
    _params = _params + i.key() + "=\"" + QUrl::toPercentEncoding(i.value()) + "\",";
    i++;
  }
  _params = _params.left(_params.length() - 1);

  QMap<QString, QString> headers;
  headers["Authorization"] = _params;

  QNetworkReply *reply = NULL;
  if (method == "POST")
    reply = ajax.post(url, headers, false);
  else reply = ajax.get(url, headers, false);
  connect(reply, SIGNAL(finished()), this, SLOT(accessFinished()));
  connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(accessError(QNetworkReply::NetworkError)));

   return "";
}

QNetworkReply *OAuth::requestApplication(const QString &method, const QString &url)
{
  QString sig = "";
  QMap<QString, QString> params = generateParameters();
  params["oauth_token"]        = _appKey;
  sig                          = signature(method, url, params, _consumerSecret, _appSecret);
  params["oauth_signature"]    = sig;
  params["OAuth realm"]        = "";

  QString _params = "";
  QMap<QString, QString>::iterator i = params.begin();
  while (i != params.end()) {
    _params = _params + i.key() + "=\"" + QUrl::toPercentEncoding(i.value()) + "\",";
    i++;
  }
  _params = _params.left(_params.length() - 1);

  QMap<QString, QString> headers;
  headers["Authorization"] = _params;

  QNetworkReply *reply = NULL;
  if (method.compare("POST") == 0)
    reply = ajax.post(url, headers, false);
  else reply = ajax.get(url, headers, false);
  connect(reply, SIGNAL(finished()), this, SLOT(appFinished()));
  connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(appError(QNetworkReply::NetworkError)));

  return reply;
}

QNetworkReply *OAuth::requestApplication(const QString &method, const QString &url,
  const QMap<QString, QString> &httpparam)
{
  QString sig = "";
  QString post_data = "";
  QMap<QString, QString> params = generateParameters();
  params["oauth_token"]        = _appKey;
  QMap<QString, QString>::const_iterator j = httpparam.begin();
  while (j != httpparam.end()) {
    post_data += (j.key() + "=" + QUrl::toPercentEncoding(j.value()) + "&");
    params[j.key()] = QUrl::toPercentEncoding(j.value());
    j++;
  }
  if (post_data.length() > 0) post_data = post_data.left(post_data.length() - 1);
  sig                          = signature(method, url, params, _consumerSecret, _appSecret);
  params["oauth_signature"]    = sig;
  params["OAuth realm"]        = "";

  QString _params = "";
  QMap<QString, QString>::iterator i = params.begin();
  while (i != params.end()) {
    _params = _params + i.key() + "=\"" + QUrl::toPercentEncoding(i.value()) + "\",";
    i++;
  }
  _params = _params.left(_params.length() - 1);

  QMap<QString, QString> headers;
  headers["Authorization"] = _params;

  QNetworkReply *reply = NULL;
  if (method.compare("POST") == 0)
    reply = ajax.post(url, headers, post_data);
  else reply = ajax.get(url, headers, false);
  connect(reply, SIGNAL(finished()), this, SLOT(appFinished()));
  connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(appError(QNetworkReply::NetworkError)));

  return reply;
}

void OAuth::appFinished()
{
  QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());

//  qDebug() <<  " appFinished " << reply->rawHeaderList() << reply->error() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);

  if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) {
    emit appRequestFinish(reply->url(), QString::fromUtf8(reply->readAll()));
  } else if (reply->error() > 100) {
    emit appRequestError(reply->url(), QString::fromUtf8(reply->readAll()));
  } else { // Network Error
    emit appNetworkError();
  }
}

void OAuth::appError(QNetworkReply::NetworkError err)
{
  Q_UNUSED(err);

//  QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());

//  qDebug() <<  " appError " << reply->rawHeaderList();

//  qDebug() << reply->readAll();
}

QString OAuth::consumerKey()
{
  return _consumerKey;
}

void OAuth::setConsumerKey(QString key)
{
  _consumerKey = key;
}

QString OAuth::consumerSecret()
{
  return _consumerSecret;
}

void OAuth::setConsumerSecret(QString secret)
{
  _consumerSecret = secret;
}

QString OAuth::tokenKey()
{
  return _tokenKey;
}

QString OAuth::tokenSecret()
{
  return _tokenSecret;
}

void OAuth::setTokenVerifier(QString ver)
{
  _tokenVerifier = ver;
}

QString OAuth::appKey()
{
   return _appKey;
}

QString OAuth::appSecret()
{
   return _appSecret;
}

void OAuth::setAppKey(const QString &appKey)
{
   _appKey = appKey;
}

void OAuth::setAppSecret(const QString &appSecret)
{
   _appSecret = appSecret;
}
